Micron Document
NexusPi Git Node

Node / mirrors / RTNode-HeltecV4 / files / TcpInterface.h

Displaying Raw • Download

TcpInterface.h main (ee519cc2) Text, 25.87 KB

T8b949e// Copyright (C) 2026, Firewall Mode Extension
T8b949e// Based on microReticulum_Firmware by Mark Qvist
T8b949e//
T8b949e// TcpInterface — An RNS InterfaceImpl that bridges a WiFi TCP
T8b949e// connection as an RNS transport interface. Used for both the
T8b949e// backbone (BackboneInterface) and local AP (LocalTcpInterface).
T8b949e//
T8b949e// This program is free software: you can redistribute it and/or modify
T8b949e// it under the terms of the GNU General Public License as published by
T8b949e// the Free Software Foundation, either version 3 of the License, or
T8b949e// (at your option) any later version.

Tff7b72#Tff7b72ifndef TCP_INTERFACE_H
Tff7b72#Tff7b72define TCP_INTERFACE_H

Tff7b72#Tff7b72ifdef HAS_RNS
Tff7b72#Tff7b72ifdef FIREWALL_MODE

Tff7b72#Tff7b72include T8b949e<WiFi.h>
Tff7b72#Tff7b72include T8b949e<lwip/sockets.h>T8b949e // SO_LINGER — force RST to free lwIP PCBs immediately
Tff7b72#Tff7b72include T8b949e<netinet/tcp.h>T8b949e // TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT
Tff7b72#Tff7b72include T8b949e<Interface.h>
Tff7b72#Tff7b72include T8b949e<Transport.h>
Tff7b72#Tff7b72include T8b949e<Bytes.h>

T8b949e// ─── TCP Interface Configuration ─────────────────────────────────────────────
Tff7b72#Tff7b72define TCP_IF_DEFAULT_PORT 4242
Tff7b72#Tff7b72ifdef FIREWALL_MODE
Tff7b72#Tff7b72define TCP_IF_MAX_CLIENTS 8
Tff7b72#Tff7b72else
Tff7b72#Tff7b72define TCP_IF_MAX_CLIENTS 4
Tff7b72#Tff7b72endif
Tff7b72#Tff7b72define TCP_IF_HW_MTU 1064
Tff7b72#Tff7b72define TCP_IF_CONNECT_TIMEOUT 6000 T8b949e// ms
Tff7b72#Tff7b72define TCP_IF_WRITE_TIMEOUT 2000 T8b949e// ms — short to avoid WDT
Tff7b72#Tff7b72define TCP_IF_READ_TIMEOUT 120000 T8b949e// ms — 2 minutes (backbone can go quiet)
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MIN 30000 T8b949e// ms — initial reconnect interval (30s, was 10s)
Tff7b72#Tff7b72define TCP_IF_RECONNECT_MAX 600000 T8b949e// ms — max backoff (10 minutes, was 2 min)
Tff7b72#Tff7b72define TCP_IF_RECONNECT_GIVEUP 12 T8b949e// consecutive failures before giving up entirely
Tff7b72#Tff7b72define TCP_IF_KEEPALIVE_INTERVAL 30000 T8b949e// ms — send empty HDLC frames to keep link alive
Tff7b72#Tff7b72define TCP_IF_POLL_INTERVAL 10 T8b949e// ms

T8b949e// HDLC-like framing for TCP (matches Reticulum-rust tcp_interface)
Tff7b72#Tff7b72define HDLC_FLAG 0x7E
Tff7b72#Tff7b72define HDLC_ESC 0x7D
Tff7b72#Tff7b72define HDLC_ESC_MASK 0x20

Tff7b72#Tff7b72define KISS_FEND 0xC0
Tff7b72#Tff7b72define KISS_FESC 0xDB
Tff7b72#Tff7b72define KISS_TFEND 0xDC
Tff7b72#Tff7b72define KISS_TFESC 0xDD
Tff7b72#Tff7b72define KISS_CMD_DATA 0x00

Tff7b72enum Te6edf3TcpFraming Tb4b4b4{
Te6edf3TCP_FRAMING_AUTO Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3TCP_FRAMING_HDLCTb4b4b4,
Te6edf3TCP_FRAMING_KISSTb4b4b4,
Tb4b4b4}Tb4b4b4;

T8b949e// ─── TCP Interface Mode ──────────────────────────────────────────────────────
Tff7b72enum Te6edf3TcpIfMode Tb4b4b4{
Te6edf3TCP_IF_MODE_SERVER Tff7b72= T79c0ff0Tb4b4b4, T8b949e// Listen for incoming connections (from backbone rnsd)
Te6edf3TCP_IF_MODE_CLIENT Tff7b72= T79c0ff1Tb4b4b4, T8b949e// Connect out to a backbone rnsd TCP server
Tb4b4b4}Tb4b4b4;

T8b949e// ─── Client connection state ─────────────────────────────────────────────────
Tff7b72struct T56d364TcpClient Tb4b4b4{
Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Tffa657uint32_t Te6edf3last_activityTb4b4b4;
Tffa657bool Te6edf3activeTb4b4b4;
Te6edf3TcpFraming Te6edf3framingTb4b4b4;
Tffa657bool Te6edf3in_frameTb4b4b4;
Tffa657bool Te6edf3escapeTb4b4b4;
Tffa657bool Te6edf3truncatedTb4b4b4;
Tffa657uint8_t Te6edf3kiss_commandTb4b4b4;
Tffa657uint8_t Te6edf3rxbufTb4b4b4[Te6edf3TCP_IF_HW_MTUTb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3rxlenTb4b4b4;
Tb4b4b4}Tb4b4b4;

T8b949e// ─── TcpInterface Class ─────────────────────────────────────────────────────
Te6edf3class Te6edf3TcpInterface Tff7b72: Te6edf3public Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImpl Tb4b4b4{
Te6edf3publicTff7b72:
Te6edf3TcpInterfaceTb4b4b4(Te6edf3TcpIfMode Te6edf3modeTb4b4b4, Tffa657uint16_t Te6edf3port Tff7b72= Te6edf3TCP_IF_DEFAULT_PORTTb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3target_host Tff7b72= Tffa657nullptrTb4b4b4, Tffa657uint16_t Te6edf3target_port Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72const Tffa657charTff7b72* Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffBackboneInterfaceTa5d6ff"Tb4b4b4)
Tff7b72: Te6edf3RNSTff7b72:Tff7b72:Te6edf3InterfaceImplTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4,
Te6edf3_modeTb4b4b4(Te6edf3modeTb4b4b4)Tb4b4b4,
Te6edf3_portTb4b4b4(Te6edf3portTb4b4b4)Tb4b4b4,
Te6edf3_target_portTb4b4b4(Te6edf3target_portTb4b4b4)Tb4b4b4,
Te6edf3_serverTb4b4b4(Tffa657nullptrTb4b4b4)Tb4b4b4,
Te6edf3_num_clientsTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_reconnectTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_last_keepaliveTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_reconnect_intervalTb4b4b4(Te6edf3TCP_IF_RECONNECT_MINTb4b4b4)Tb4b4b4,
Te6edf3_read_timeoutTb4b4b4(Te6edf3TCP_IF_READ_TIMEOUTTb4b4b4)Tb4b4b4,
Te6edf3_resolved_ipTb4b4b4(Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_consecutive_failuresTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3_startedTb4b4b4(Tffa657falseTb4b4b4)
Tb4b4b4{
Te6edf3_IN Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_OUT Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_HW_MTU Tff7b72= Te6edf3TCP_IF_HW_MTUTb4b4b4;
T8b949e// v1.0.12: Tell Transport this interface has a known fixed MTU,
T8b949e// enabling link MTU clamping when forwarding LINKREQUEST packets.
Te6edf3_FIXED_MTU Tff7b72= Tffa657trueTb4b4b4;
T8b949e// TCP links are effectively 10 Mbps+. Setting a realistic
T8b949e// bitrate lets Transport prefer TCP paths over LoRa when
T8b949e// both exist for the same destination.
T8b949e// announce_cap = 2% keeps backbone announce flooding in check.
Te6edf3_bitrate Tff7b72= T79c0ff10000000Tb4b4b4;
Te6edf3_announce_cap Tff7b72= T79c0ff2.0Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3target_host Tff7b72!Tff7b72= Tffa657nullptrTb4b4b4) Tb4b4b4{
Te6edf3strncpyTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3target_hostTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4)Tb4b4b4;
Te6edf3_target_hostTb4b4b4[Tff7b72sizeofTb4b4b4(Te6edf3_target_hostTb4b4b4) Tff7b72- T79c0ff1Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4;
Tb4b4b4}
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Te6edf3virtual Tff7b72~Te6edf3TcpInterfaceTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Lifecycle ───────────────────────────────────────────────────────────
Tffa657bool Te6edf3startTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_startedTb4b4b4) Tff7b72return Tffa657trueTb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVERTb4b4b4) Tb4b4b4{
Te6edf3_server Tff7b72= Te6edf3new Te6edf3WiFiServerTb4b4b4(Te6edf3_portTb4b4b4, Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3beginTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_serverTff7b72-Tff7b72>Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Server listening on port %dTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_portTb4b4b4)Tb4b4b4;
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Client mode — try initial connection
Te6edf3_started Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Te6edf3_startedTb4b4b4;
Tb4b4b4}

Tffa657void Te6edf3stopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
T8b949e// Force RST to free lwIP PCBs immediately (no TIME_WAIT)
Tffa657int Te6edf3fd Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3_serverTff7b72-Tff7b72>Te6edf3endTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3delete Te6edf3_serverTb4b4b4;
Te6edf3_server Tff7b72= Tffa657nullptrTb4b4b4;
Tb4b4b4}
Te6edf3_started Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Main loop — call from Arduino loop() ────────────────────────────────
Tffa657void Te6edf3loopTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_startedTb4b4b4) Tff7b72returnTb4b4b4;

T8b949e// Accept new connections in server mode
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_SERVER Tff7b72&Tff7b72& Te6edf3_serverTb4b4b4) Tb4b4b4{
Te6edf3WiFiClient Te6edf3newClient Tff7b72= Te6edf3_serverTff7b72-Tff7b72>Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3newClientTb4b4b4) Tb4b4b4{
Te6edf3_accept_clientTb4b4b4(Te6edf3newClientTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

T8b949e// Client mode reconnection (with WiFi check + exponential backoff)
Tff7b72if Tb4b4b4(Te6edf3_mode Tff7b72=Tff7b72= Te6edf3TCP_IF_MODE_CLIENT Tff7b72&Tff7b72& Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// _reconnect_interval > TCP_IF_RECONNECT_MAX means we've given up
Tff7b72if Tb4b4b4(Te6edf3_reconnect_interval Tff7b72> Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4) Tff7b72returnTb4b4b4;
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_reconnect Tff7b72>Tff7b72= Te6edf3_reconnect_intervalTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3statusTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= Te6edf3WL_CONNECTEDTb4b4b4) Tb4b4b4{
Te6edf3_connect_clientTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// WiFi not connected — skip TCP attempt, just update timer
Te6edf3_last_reconnect Tff7b72= Te6edf3nowTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// Send keepalive (empty HDLC frames) to detect dead connections quickly.
T8b949e// Checking the write result catches half-open connections (remote host
T8b949e// disappeared without sending FIN) without waiting for the read timeout.
Tff7b72if Tb4b4b4(Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657uint32_t Te6edf3now Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3now Tff7b72- Te6edf3_last_keepalive Tff7b72>Tff7b72= Te6edf3TCP_IF_KEEPALIVE_INTERVALTb4b4b4) Tb4b4b4{
Te6edf3_last_keepalive Tff7b72= Te6edf3nowTb4b4b4;
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4) Tff7b72continueTb4b4b4;
Tffa657uint8_t Te6edf3delimiter Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_KISS Tff7b72? Te6edf3KISS_FEND Tff7b72: Te6edf3HDLC_FLAGTb4b4b4;
Tffa657uint8_t Te6edf3keepaliveTb4b4b4[Tb4b4b4] Tff7b72= Tb4b4b4{ Te6edf3delimiterTb4b4b4, Te6edf3delimiter Tb4b4b4}Tb4b4b4;
Tffa657size_t Te6edf3written Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3keepaliveTb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3written Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffkeepalive write failedTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// Process incoming data from all active clients
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tff7b72continueTb4b4b4;

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffdisconnectedTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}

T8b949e// Check read timeout (0 = disabled)
Tff7b72if Tb4b4b4(Te6edf3_read_timeout Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72> T79c0ff0 Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3millisTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activityTb4b4b4) Tff7b72> Te6edf3_read_timeoutTb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffread timeoutTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72continueTb4b4b4;
Tb4b4b4}

T8b949e// Read available bytes and deframe
Tff7b72while Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3availableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3byte Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3readTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_hdlc_deframeTb4b4b4(Te6edf3iTb4b4b4, Te6edf3byteTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Stats ───────────────────────────────────────────────────────────────
Tffa657int Te6edf3clientCountTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clientsTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isStartedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_startedTb4b4b4; Tb4b4b4}
Tffa657bool Te6edf3isConnectedTb4b4b4(Tb4b4b4) Tff7b72const Tb4b4b4{ Tff7b72return Te6edf3_num_clients Tff7b72> T79c0ff0Tb4b4b4; Tb4b4b4}
Tffa657void Te6edf3setReadTimeoutTb4b4b4(Tffa657uint32_t Te6edf3timeout_msTb4b4b4) Tb4b4b4{ Te6edf3_read_timeout Tff7b72= Te6edf3timeout_msTb4b4b4; Tb4b4b4}

Te6edf3protectedTff7b72:
T8b949e// ─── RNS InterfaceImpl: outgoing packet from RNS Transport ───────────────
Te6edf3virtual Tffa657void Te6edf3send_outgoingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_started Tff7b72|Tff7b72| Te6edf3_num_clients Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tff7b72returnTb4b4b4;

T8b949e// Frame the data. Use a static buffer (BSS, not stack) to avoid
T8b949e// "Stack canary watchpoint" crashes in deep call chains. The frame can
T8b949e// be up to 2x the input size (every byte escaped) + 2 FLAG delimiters.
T8b949e// Single-threaded context (loopTask) so static is safe across all
T8b949e// TcpInterface instances.
Tff7b72const Tffa657size_t Te6edf3frame_cap Tff7b72= Te6edf3TCP_IF_HW_MTU Tff7b72* T79c0ff2 Tff7b72+ T79c0ff4Tb4b4b4;
Tff7b72static Tffa657uint8_t Te6edf3frame_bufTb4b4b4[Te6edf3frame_capTb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3flen Tff7b72= T79c0ff0Tb4b4b4;

T8b949e// Suppress only a byte-for-byte echo to the source client. Distinct
T8b949e// packets generated synchronously in response (path announces, proofs,
T8b949e// etc.) must be returned on that same connection.
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3i Tff7b72=Tff7b72= Te6edf3_last_rx_client_idx Tff7b72&Tff7b72& Te6edf3data Tff7b72=Tff7b72= Te6edf3_last_rx_dataTb4b4b4) Tb4b4b4{
Tff7b72continueTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72&Tff7b72& Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3connectedTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3TcpFraming Te6edf3framing Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3framingTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4) Tff7b72continueTb4b4b4;

Te6edf3flen Tff7b72= T79c0ff0Tb4b4b4;
Tffa657uint8_t Te6edf3delimiter Tff7b72= Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_KISS Tff7b72? Te6edf3KISS_FEND Tff7b72: Te6edf3HDLC_FLAGTb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3delimiterTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_KISSTb4b4b4) Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3KISS_CMD_DATATb4b4b4;
Tff7b72for Tb4b4b4(Tffa657size_t Te6edf3offset Tff7b72= T79c0ff0Tb4b4b4; Te6edf3offset Tff7b72< Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4; Te6edf3offsetTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tffa657uint8_t Te6edf3byte Tff7b72= Te6edf3dataTb4b4b4.Te6edf3dataTb4b4b4(Tb4b4b4)Tb4b4b4[Te6edf3offsetTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_KISS Tff7b72&Tff7b72& Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FEND Tff7b72|Tff7b72| Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FESCTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3KISS_FESCTb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FEND Tff7b72? Te6edf3KISS_TFEND Tff7b72: Te6edf3KISS_TFESCTb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_HDLC Tff7b72&Tff7b72& Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_FLAG Tff7b72|Tff7b72| Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3HDLC_ESCTb4b4b4;
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byte Tff7b72^ Te6edf3HDLC_ESC_MASKTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3flen Tff7b72>Tff7b72= Te6edf3frame_cap Tff7b72- T79c0ff4Tb4b4b4) Tff7b72breakTb4b4b4;
Tb4b4b4}
Te6edf3frame_bufTb4b4b4[Te6edf3flenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3delimiterTb4b4b4;
Tffa657size_t Te6edf3written Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3frame_bufTb4b4b4, Te6edf3flenTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3written Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3_cleanup_clientTb4b4b4(Te6edf3iTb4b4b4, Ta5d6ff"Ta5d6ffwrite failedTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3yieldTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// feed WDT between TCP writes and RNS processing

T8b949e// Post-send housekeeping
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_outgoingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── RNS InterfaceImpl: incoming packet to RNS Transport ─────────────────
Te6edf3virtual Tffa657void Te6edf3handle_incomingTb4b4b4(Tff7b72const Te6edf3RNSTff7b72:Tff7b72:Te6edf3BytesTff7b72& Te6edf3dataTb4b4b4) Te6edf3override Tb4b4b4{
Te6edf3TRACEFTb4b4b4(Ta5d6ff"Ta5d6ffTcpInterface.handle_incoming: (%u bytes)Ta5d6ff"Tb4b4b4, Te6edf3dataTb4b4b4.Te6edf3sizeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3InterfaceImplTff7b72:Tff7b72:Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3privateTff7b72:
T8b949e// ─── Cleanup a client slot, freeing all lwIP resources ───────────────────
Tffa657void Te6edf3_cleanup_clientTb4b4b4(Tffa657int Te6edf3idxTb4b4b4, Tff7b72const Tffa657charTff7b72* Te6edf3reasonTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3cTb4b4b4.Te6edf3activeTb4b4b4) Tff7b72returnTb4b4b4;

Tffa657uint32_t Te6edf3heap_before Tff7b72= Te6edf3ESPTb4b4b4.Te6edf3getFreeHeapTb4b4b4(Tb4b4b4)Tb4b4b4;

T8b949e// Set SO_LINGER with timeout 0: forces RST instead of FIN,
T8b949e// which skips TIME_WAIT and immediately frees the lwIP PCB
T8b949e// and all associated TCP send/receive buffers (~2-4 KB each).
Tffa657int Te6edf3fd Tff7b72= Te6edf3cTb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Te6edf3cTb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// Release any residual shared_ptr state
Te6edf3cTb4b4b4.Te6edf3active Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_num_clientsTff7b72-Tff7b72-Tb4b4b4;

Tffa657uint32_t Te6edf3heap_after Tff7b72= Te6edf3ESPTb4b4b4.Te6edf3getFreeHeapTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d %s (heap: %u -> %u, delta: %+d)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3idxTb4b4b4, Te6edf3reasonTb4b4b4, Te6edf3heap_beforeTb4b4b4, Te6edf3heap_afterTb4b4b4,
Tb4b4b4(Tffa657intTb4b4b4)Tb4b4b4(Te6edf3heap_after Tff7b72- Te6edf3heap_beforeTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}

Tffa657void Te6edf3_deliver_frameTb4b4b4(Tffa657int Te6edf3idxTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Td2a8ffdataTb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4, Te6edf3cTb4b4b4.Te6edf3rxlenTb4b4b4)Tb4b4b4;
Te6edf3_last_rx_client_idx Tff7b72= Te6edf3idxTb4b4b4;
Te6edf3_last_rx_data Tff7b72= Te6edf3dataTb4b4b4;
Te6edf3handle_incomingTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4;
Te6edf3_last_rx_dataTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_last_rx_client_idx Tff7b72= T79c0ff-1Tb4b4b4;
Tb4b4b4}

T8b949e// ─── HDLC/KISS byte-level deframing ─────────────────────────────────────
Tffa657void Te6edf3_hdlc_deframeTb4b4b4(Tffa657int Te6edf3idxTb4b4b4, Tffa657uint8_t Te6edf3byteTb4b4b4) Tb4b4b4{
Te6edf3TcpClientTff7b72& Te6edf3c Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3idxTb4b4b4]Tb4b4b4;

Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_FLAGTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_HDLCTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d framing: HDLCTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3idxTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FENDTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_KISSTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d framing: KISSTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3idxTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72returnTb4b4b4;
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657trueTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3framing Tff7b72=Tff7b72= Te6edf3TCP_FRAMING_KISSTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FENDTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72&Tff7b72& Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72=Tff7b72= Te6edf3KISS_CMD_DATA Tff7b72&Tff7b72& Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Tff7b72!Te6edf3cTb4b4b4.Te6edf3truncatedTb4b4b4) Tb4b4b4{
Te6edf3_deliver_frameTb4b4b4(Te6edf3idxTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3truncatedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DROPPED oversized KISS frame from client %d (>%d bytes)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3idxTb4b4b4, Te6edf3TCP_IF_HW_MTUTb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657trueTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3cTb4b4b4.Te6edf3in_frameTb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72=Tff7b72= T79c0ff0xFFTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72= Te6edf3byte Tff7b72& T79c0ff0x0FTb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3kiss_command Tff7b72!Tff7b72= Te6edf3KISS_CMD_DATATb4b4b4) Tff7b72returnTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3escapeTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_TFENDTb4b4b4) Te6edf3byte Tff7b72= Te6edf3KISS_FENDTb4b4b4;
Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_TFESCTb4b4b4) Te6edf3byte Tff7b72= Te6edf3KISS_FESCTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3KISS_FESCTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657trueTb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tff7b72else Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657trueTb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_FLAGTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72&Tff7b72& Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
T8b949e// v1.0.12: If the frame exceeded the buffer, drop it entirely
T8b949e// instead of delivering a truncated/corrupt packet to Transport.
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3truncatedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DROPPED oversized frame from client %d (>%d bytes, buffered %u)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3idxTb4b4b4, Te6edf3TCP_IF_HW_MTUTb4b4b4, Te6edf3cTb4b4b4.Te6edf3rxlenTb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// End of frame — deliver to RNS
T8b949e// v1.0.10: Set _last_rx_client_idx so send_outgoing() can
T8b949e// skip echoing this packet back to the client that sent it.
T8b949e// The entire call chain (handle_incoming → Transport::inbound
T8b949e// → transmit → send_outgoing) is synchronous, so this is safe.
Te6edf3_deliver_frameTb4b4b4(Te6edf3idxTb4b4b4)Tb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Te6edf3cTb4b4b4.Te6edf3in_frame Tff7b72= Tffa657trueTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3in_frameTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3escapeTb4b4b4) Tb4b4b4{
Te6edf3byte Tff7b72^Tff7b72= Te6edf3HDLC_ESC_MASKTb4b4b4;
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3byte Tff7b72=Tff7b72= Te6edf3HDLC_ESCTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3escape Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3cTb4b4b4.Te6edf3rxlen Tff7b72< Te6edf3TCP_IF_HW_MTUTb4b4b4) Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3rxbufTb4b4b4[Te6edf3cTb4b4b4.Te6edf3rxlenTff7b72+Tff7b72+Tb4b4b4] Tff7b72= Te6edf3byteTb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3cTb4b4b4.Te6edf3truncated Tff7b72= Tffa657trueTb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// ─── Accept a new server-mode client ─────────────────────────────────────
Tffa657void Te6edf3_accept_clientTb4b4b4(Te6edf3WiFiClientTff7b72& Te6edf3newClientTb4b4b4) Tb4b4b4{
T8b949e// Find a free slot
Tff7b72for Tb4b4b4(Tffa657int Te6edf3i Tff7b72= T79c0ff0Tb4b4b4; Te6edf3i Tff7b72< Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4; Te6edf3iTff7b72+Tff7b72+Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3activeTb4b4b4) Tb4b4b4{
T8b949e// Defensive: force-release any residual lwIP resources in this slot
T8b949e// before assigning the new client (prevents PCB/buffer leaks)
Tffa657int Te6edf3fd Tff7b72= Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72struct T56d364linger Te6edf3linTb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_onoff Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3linTb4b4b4.Te6edf3l_linger Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_LINGERTb4b4b4, Tff7b72&Te6edf3linTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3linTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3WiFiClientTb4b4b4(Tb4b4b4)Tb4b4b4; T8b949e// Reset to clean state

Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3newClientTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_AUTOTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clientsTff7b72+Tff7b72+Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Client %d connected from %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3iTb4b4b4, Te6edf3_clientsTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3clientTb4b4b4.Te6edf3remoteIPTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e// No free slots — reject
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Max clients reached, rejecting connectionTa5d6ff"Tb4b4b4)Tb4b4b4;
Te6edf3newClientTb4b4b4.Te6edf3stopTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Client-mode outbound connection ─────────────────────────────────────
Tffa657void Te6edf3_connect_clientTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3_target_hostTb4b4b4[T79c0ff0Tb4b4b4] Tff7b72=Tff7b72= Ta5d6ff'Ta5d6ff\0Ta5d6ff'Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] No target host configured for client modeTa5d6ff"Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}

Te6edf3WiFiClient Te6edf3clientTb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_CONNECT_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;

Tffa657bool Te6edf3connected Tff7b72= Tffa657falseTb4b4b4;

T8b949e// Try cached IP first (avoids DNS lookup on every reconnect)
Tff7b72if Tb4b4b4(Te6edf3_resolved_ip Tff7b72!Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (cached IP)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3_resolved_ipTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
T8b949e// Cached IP failed — clear cache and try fresh DNS
Te6edf3_resolved_ip Tff7b72= Tb4b4b4(Tffa657uint32_tTb4b4b4)T79c0ff0Tb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printlnTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Cached IP failed, retrying with DNSTa5d6ff"Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connecting to %s:%d (DNS)...Tffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Te6edf3IPAddress Te6edf3resolvedTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3WiFiTb4b4b4.Te6edf3hostByNameTb4b4b4(Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3_resolved_ip Tff7b72= Te6edf3resolvedTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Resolved %s -> %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4, Te6edf3resolvedTb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3c_strTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3connected Tff7b72= Te6edf3clientTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3resolvedTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] DNS failed for %sTffea00\rTffea00\nTa5d6ff"Tb4b4b4, Te6edf3_target_hostTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3connectedTb4b4b4) Tb4b4b4{
Te6edf3clientTb4b4b4.Te6edf3setNoDelayTb4b4b4(Tffa657trueTb4b4b4)Tb4b4b4;
Te6edf3clientTb4b4b4.Te6edf3setTimeoutTb4b4b4(Te6edf3TCP_IF_WRITE_TIMEOUT Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
T8b949e// Enable OS-level TCP keepalives so the kernel probes the connection
T8b949e// after 10s idle, retrying every 5s up to 3 times (15s detection window).
T8b949e// This catches half-open connections independently of our app-layer keepalive.
Tffa657int Te6edf3fd Tff7b72= Te6edf3clientTb4b4b4.Te6edf3fdTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3fd Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tffa657int Te6edf3optval Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3SOL_SOCKETTb4b4b4, Te6edf3SO_KEEPALIVETb4b4b4, Tff7b72&Te6edf3optvalTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3optvalTb4b4b4)Tb4b4b4)Tb4b4b4;
Tffa657int Te6edf3idle Tff7b72= T79c0ff10Tb4b4b4; T8b949e// seconds idle before first probe
Tffa657int Te6edf3intvl Tff7b72= T79c0ff5Tb4b4b4; T8b949e// seconds between retries
Tffa657int Te6edf3cnt Tff7b72= T79c0ff3Tb4b4b4; T8b949e// retries before declaring dead
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3IPPROTO_TCPTb4b4b4, Te6edf3TCP_KEEPIDLETb4b4b4, Tff7b72&Te6edf3idleTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3idleTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3IPPROTO_TCPTb4b4b4, Te6edf3TCP_KEEPINTVLTb4b4b4, Tff7b72&Te6edf3intvlTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3intvlTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3setsockoptTb4b4b4(Te6edf3fdTb4b4b4, Te6edf3IPPROTO_TCPTb4b4b4, Te6edf3TCP_KEEPCNTTb4b4b4, Tff7b72&Te6edf3cntTb4b4b4, Tff7b72sizeofTb4b4b4(Te6edf3cntTb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3client Tff7b72= Te6edf3clientTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3active Tff7b72= Tffa657trueTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3framing Tff7b72= Te6edf3TCP_FRAMING_HDLCTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3in_frame Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3escape Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3truncated Tff7b72= Tffa657falseTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3kiss_command Tff7b72= T79c0ff0xFFTb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3rxlen Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_clientsTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4.Te6edf3last_activity Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3_num_clients Tff7b72= T79c0ff1Tb4b4b4;
Te6edf3_consecutive_failures Tff7b72= T79c0ff0Tb4b4b4;
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MINTb4b4b4;
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Connected to backbone at %s:%dTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3_consecutive_failuresTff7b72+Tff7b72+Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3_consecutive_failures Tff7b72>Tff7b72= Te6edf3TCP_IF_RECONNECT_GIVEUPTb4b4b4) Tb4b4b4{
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Giving up on %s:%d after %d failuresTffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4, Te6edf3_consecutive_failuresTb4b4b4)Tb4b4b4;
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MAX Tff7b72+ T79c0ff1Tb4b4b4; T8b949e// signal: don't retry
Te6edf3_last_reconnect Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4}
T8b949e// Exponential backoff: 30s -> 60s -> 120s -> 240s -> 480s -> 600s (max)
Te6edf3_reconnect_interval Tff7b72= Te6edf3_reconnect_interval Tff7b72* T79c0ff2Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3_reconnect_interval Tff7b72> Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4) Tb4b4b4{
Te6edf3_reconnect_interval Tff7b72= Te6edf3TCP_IF_RECONNECT_MAXTb4b4b4;
Tb4b4b4}
Te6edf3SerialTb4b4b4.Te6edf3printfTb4b4b4(Ta5d6ff"Ta5d6ff[TcpIF] Failed to connect to %s:%d (attempt %d, next retry in %ds)Tffea00\rTffea00\nTa5d6ff"Tb4b4b4,
Te6edf3_target_hostTb4b4b4, Te6edf3_target_portTb4b4b4, Te6edf3_consecutive_failuresTb4b4b4,
Te6edf3_reconnect_interval Tff7b72/ T79c0ff1000Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Te6edf3_last_reconnect Tff7b72= Te6edf3millisTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}

T8b949e// ─── Member variables ────────────────────────────────────────────────────
Te6edf3TcpIfMode Te6edf3_modeTb4b4b4;
Tffa657uint16_t Te6edf3_portTb4b4b4;
Tffa657char Te6edf3_target_hostTb4b4b4[T79c0ff64Tb4b4b4]Tb4b4b4;
Tffa657uint16_t Te6edf3_target_portTb4b4b4;
Te6edf3WiFiServerTff7b72* Te6edf3_serverTb4b4b4;
Te6edf3TcpClient Te6edf3_clientsTb4b4b4[Te6edf3TCP_IF_MAX_CLIENTSTb4b4b4]Tb4b4b4;
Tffa657int Te6edf3_num_clientsTb4b4b4;
Tffa657uint32_t Te6edf3_last_reconnectTb4b4b4;
Tffa657uint32_t Te6edf3_last_keepaliveTb4b4b4;
Tffa657uint32_t Te6edf3_reconnect_intervalTb4b4b4;
Tffa657uint32_t Te6edf3_read_timeoutTb4b4b4;
Te6edf3IPAddress Te6edf3_resolved_ipTb4b4b4;
Tffa657uint16_t Te6edf3_consecutive_failuresTb4b4b4;
Tffa657bool Te6edf3_startedTb4b4b4;
Tffa657int Te6edf3_last_rx_client_idx Tff7b72= T79c0ff-1Tb4b4b4; T8b949e// v1.0.10: echo prevention — tracks which client is currently delivering an inbound frame
Te6edf3RNSTff7b72:Tff7b72:Te6edf3Bytes Te6edf3_last_rx_dataTb4b4b4;
Tb4b4b4}Tb4b4b4;

Tff7b72#Tff7b72endif T8b949e// FIREWALL_MODE
Tff7b72#Tff7b72endif T8b949e// HAS_RNS
Tff7b72#Tff7b72endif T8b949e// TCP_INTERFACE_H

Served by rngit 1.4.2 - Generated in 0.07s